package prickworlds.game;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glTranslatef;

import java.util.ArrayList;

import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;

import prickworlds.game.templates.MapTemplate;
import prickworlds.structure.KeyEvent;
import prickworlds.structure.MouseEvent;
import prickworlds.structure.ProgramState;
import prickworlds.util.Vec2f;

/**
 * The main game class
 * 
 * @author Faruk Acibal, Richard Dziambor, Christian Holzreuter, Nicolas Winkler
 * 
 */
public class Game implements ProgramState {
	/**
	 * constant that defines the key that moves upwards
	 */
	private static final int UP = Keyboard.KEY_W;
	/**
	 * constant that defines the key that moves downwards
	 */
	private static final int DOWN = Keyboard.KEY_S;
	/**
	 * constant that defines the key that moves leftwards
	 */
	private static final int LEFT = Keyboard.KEY_A;
	/**
	 * constant that defines the key that moves rightwards
	 */
	private static final int RIGHT = Keyboard.KEY_D;

	/**
	 * {@code true} if the upwards-key is pressed
	 */
	private boolean upPressed;
	/**
	 * {@code true} if the downwards-key is pressed
	 */
	private boolean downPressed;
	/**
	 * {@code true} if the leftwards-key is pressed
	 */
	private boolean leftPressed;
	/**
	 * {@code true} if the rightwards-key is pressed
	 */
	private boolean rightPressed;

	/**
	 * x-coordinate of the current mouse position
	 */
	private float mouseX;
	/**
	 * y-coordinate of the current mouse position
	 */
	private float mouseY;
	/**
	 * {@code true} if the mouse is currently pressed
	 */
	private boolean mousePressed;

	/**
	 * true, if the game isn't paused
	 */
	private boolean running;

	/**
	 * the map with the robots on it
	 */
	Map map;

	/**
	 * the value by which the map is translated in screen coordinates (pixels)
	 */
	Vec2f mapTranslate;

	InGameMenu ingameMenu;

	/**
	 * creates a {@link Game}
	 */
	public Game() {

		upPressed = false;
		downPressed = false;
		leftPressed = false;
		rightPressed = false;
		mousePressed = false;

		running = true;

		map = new Map(MapTemplate.loadMapTemplate("data/map/map.xml"));
		mapTranslate = new Vec2f(0, 0);

		ingameMenu = new InGameMenu();
	}

	@Override
	public void update() {
		if (running) {
			map.getPlayer().setLookAt(screenToMapX(mouseX),
					screenToMapY(mouseY));
			map.getPlayer().update(upPressed, downPressed, leftPressed,
					rightPressed);

			for (int i = 0; i < map.getWalls().size(); i++) {
				map.getPlayer().processCollision(map.getWalls().get(i));
			}

			updateMapTranslation();

			map.update();

			updateBullets();
			updateEnemies();

			ingameMenu.update();
			ingameMenu.updateLife(map.getPlayer());
		}

		ingameMenu.update();
	}

	private void updateMapTranslation() {
		float playerX = map.getPlayer().posX;
		float playerY = map.getPlayer().posY;
		playerX *= map.getCellSize();
		playerY *= map.getCellSize();

		float transX = Display.getWidth() / 2 - playerX;
		float transY = Display.getHeight() / 2 - playerY;

		float mapWidth = map.getWidth() * map.getCellSize();
		float mapHeight = map.getHeight() * map.getCellSize();

		if (transX - Display.getWidth() < -mapWidth)
			transX = -mapWidth + Display.getWidth();
		if (transY - Display.getHeight() < -mapHeight)
			transY = -mapHeight + Display.getHeight();
		if (transX > 0)
			transX = 0;
		if (transY > 0)
			transY = 0;

		mapTranslate.x = transX;
		mapTranslate.y = transY;
	}

	/**
	 * updates the bullets
	 */
	private void updateBullets() {
		ArrayList<Bullet> bullets = map.getBullets();
		ArrayList<EnemyRobot> enemies = map.getEnemies();

		bullets: for (int i = 0; i < bullets.size(); i++) {
			Bullet bullet = bullets.get(i);
			bullet.update();

			for (int j = 0; j < enemies.size(); j++) {
				EnemyRobot enemy = enemies.get(j);
				if (enemy.getHitbox().overlaps(bullet.getHitbox())
						&& bullet.team != enemy.team) {
					enemies.get(j).addDamage(bullets.get(i).damage);
					map.addExplosion(bullet.posX, bullet.posY);
					bullets.remove(i);
					if (enemies.get(j).destroyed) {
						/*
						 * map.addExplosion(enemy.posX-0.2f, enemy.posY-0.2f);
						 * map.addExplosion(enemy.posX-0.9f, enemy.posY-0.9f);
						 * map.addExplosion(enemy.posX-0.9f, enemy.posY+0.5f);
						 * map.addExplosion(enemy.posX+0.5f, enemy.posY-0.9f);
						 * map.addExplosion(enemy.posX+0.5f, enemy.posY+0.5f);
						 */
						map.addExplosion(enemy.posX - 1.0f, enemy.posY - 1.0f,
								3.0f);

						// add chance for a drop
						if (Math.random() < 0.5) {
							DropItem dr = new DropItem(enemy.posX, enemy.posY,
									map.getTemplate().getDropType(
											(int) (Math.random() * 4)));
							map.getDrops().add(dr);
						}
						enemies.remove(j);
					}
					i--;
					continue bullets;
				}
			}

			for (int j = 0; j < map.getWalls().size(); j++) {
				if (bullet.getHitbox().overlaps(
						(map.getWalls().get(j)).getHitbox())) {
					map.addExplosion(bullet.posX, bullet.posY);
					bullets.remove(i);
					i--;
					continue bullets;
				}
			}

			if (map.getPlayer().distanceTo(bullet) < 1.2f
					&& bullet.team != map.getPlayer().team) {
				map.getPlayer().addDamage(bullets.get(i).damage);
				map.addExplosion(bullet.posX, bullet.posY);
				bullets.remove(i);
				if (map.getPlayer().destroyed) {
					// GAME OVER, BIATSCH!
					running = false;
					ingameMenu.showDeadMenu();
				}
				i--;
				continue bullets;
			}

			if (bullet.posX * bullet.posX + bullet.posY * bullet.posY > 2000 * 2000) {
				map.getBullets().remove(i);
				i--;
				continue bullets;
			}
		}

		if (mousePressed) {
			map.getPlayer().requestShot();
		}
	}

	/**
	 * updates the enemies
	 */
	private void updateEnemies() {
		map.getTemplate().robotTemplates[0].setLevel((int)(1+map.getPlayer().score/100));
		
		double spawnProbability = 0.005 + 0.0002*map.getPlayer().score;
		if (spawnProbability > 0.05) {
			spawnProbability = 0.05;
		}
		if (Math.random() < spawnProbability) {
			MapTemplate mt = map.getTemplate();
			EnemyRobot ir = new EnemyRobot(map, mt.getRobotTemplate(0),
					(float) (Math.random() * (map.getWidth() - 2) + 1),
					(float) (Math.random() * (map.getHeight() - 2) + 1), 50, 1);
			map.getEnemies().add(ir);
		}

		for (int i = 0; i < map.getEnemies().size(); i++) {
			EnemyRobot ir = map.getEnemies().get(i);
			ir.update();
			for (int j = i + 1; j < map.getEnemies().size(); j++) {
				ir.processCollision(map.getEnemies().get(j));
			}
			for (int j = 0; j < map.getWalls().size(); j++) {
				ir.processCollision(map.getWalls().get(j));
			}
			ir.processCollision(map.getPlayer());
		}
	}

	@Override
	public void display() {
		glClearColor(0.0f, 0.0f, 0.0f, 0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glPushMatrix();
		glTranslatef(mapTranslate.x, mapTranslate.y, 0.0f);
		map.display();
		glPopMatrix();
		ingameMenu.display();
	}

	@Override
	public void handleKeyEvent(KeyEvent event) {
		ingameMenu.handleKeyEvent(event);

		if (event.type == KeyEvent.Type.PRESSED) {
			if (event.key == Keyboard.KEY_ESCAPE) {
				if (running) {
					running = false;
					ingameMenu.showPauseMenu();
				} else {
					running = true;
					ingameMenu.hideMenues();
				}
				event.invalidate();
			} else if (event.key == UP) {
				upPressed = true;
				event.invalidate();
			} else if (event.key == DOWN) {
				downPressed = true;
				event.invalidate();
			} else if (event.key == LEFT) {
				leftPressed = true;
				event.invalidate();
			} else if (event.key == RIGHT) {
				rightPressed = true;
				event.invalidate();
			}
		} else if (event.type == KeyEvent.Type.RELEASED) {
			if (event.key == UP) {
				upPressed = false;
				event.invalidate();
			} else if (event.key == DOWN) {
				downPressed = false;
				event.invalidate();
			} else if (event.key == LEFT) {
				leftPressed = false;
				event.invalidate();
			} else if (event.key == RIGHT) {
				rightPressed = false;
				event.invalidate();
			}
		}
	}

	@Override
	public void handleMouseEvent(MouseEvent event) {
		ingameMenu.handleMouseEvent(event);

		if (event.type == MouseEvent.Type.MOVE) {
			mouseX = event.x;
			mouseY = event.y;
		} else if (event.type == MouseEvent.Type.LEFT_DOWN) {
			mousePressed = true;
			mouseX = event.x;
			mouseY = event.y;
		} else if (event.type == MouseEvent.Type.LEFT_UP) {
			mousePressed = false;
			mouseX = event.x;
			mouseY = event.y;
		}
	}

	public Map getMap() {
		return map;
	}

	public void setMap(Map map) {
		this.map = map;
	}

	public float getMouseX() {
		return mouseX;
	}

	public float getMouseY() {
		return mouseY;
	}

	public boolean isRunning() {
		return running;
	}

	public void setRunning(boolean running) {
		this.running = running;
	}

	/**
	 * converts a point from map coordinate system to screen coordinate system
	 * 
	 * @param pt
	 */
	public void mapToScreen(Vec2f pt) {
		int cellSize = map.getCellSize();
		pt.x = pt.x * cellSize + mapTranslate.x;
		pt.y = pt.y * cellSize + mapTranslate.y;
	}

	/**
	 * converts a point from screen coordinate system to map coordinate system
	 * 
	 * @param pt
	 */
	public void screenToMap(Vec2f pt) {
		int cellSize = map.getCellSize();
		pt.x = (pt.x - mapTranslate.x) / cellSize;
		pt.y = (pt.y - mapTranslate.y) / cellSize;
	}

	public float screenToMapX(float screenX) {
		return (screenX - mapTranslate.x) / map.getCellSize();
	}

	public float screenToMapY(float screenY) {
		return (screenY - mapTranslate.y) / map.getCellSize();
	}
}
